//go:build ignore
// +build ignore

package main

import (
	"fmt"
	"os"
	"reflect"
	"strings"

	"exti.cc/learn/gen/testmodels"
)

//go:generate
//go:generate go run l_generate.go
// Path: l_generate.go
// Compare this snippet from l_structs.go:

func main() {
	f, err := os.Create(`models\l_autogen.go`)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer f.Close()
	/*
		fmt.Fprintf(f, "//go:build ignore\n")
		fmt.Fprintf(f, "// +build ignore\n\n")
	*/

	fmt.Fprintf(f, "package testmodels\n\n")

	fmt.Fprintf(f, "import \"github.com/mitchellh/mapstructure\"\n\n")

	generateUpdateFuncs(f, reflect.TypeOf(testmodels.SubStruct{}))
	generateUpdateFuncs(f, reflect.TypeOf(testmodels.TestStruct{}))

}

func genNormalField(f *os.File, tabs, fieldName, jsonTag string, fieldType reflect.Type, isPtr bool) {
	if isPtr {
		fmt.Fprintf(f, "%s//[Value] Number,Bool,String; 由:genNormalField 函数生成\n", tabs)

		fmt.Fprintf(f, "%sif v, ok := n[\"%s\"]; ok {\n", tabs, jsonTag)
		fmt.Fprintf(f, "%s\tif nv, ok := v.(%s); ok {\n", tabs, fieldType.Elem().Name())
		fmt.Fprintf(f, "%s\t\tif nil == m.%s || nv != *m.%s { // 值不一致 \n", tabs, fieldName, fieldName)
		fmt.Fprintf(f, "%s\t\t\tif record {\n", tabs)
		if fieldType.Elem().Name() == "float64" || fieldType.Elem().Name() == "string" || fieldType.Elem().Name() == "bool" {
			fmt.Fprintf(f, "%s\t\t\t\toldval[\"%s\"] = m.%s // 保存变更\n", tabs, fieldName, fieldName)
			fmt.Fprintf(f, "%s\t\t\t}\n", tabs)
			fmt.Fprintf(f, "%s\t\t\tm.%s, changed = &nv, true\n", tabs, fieldName)
		} else {
			fmt.Fprintf(f, "%s\t\t\toldval[\"%s\"] = m.%s// 保存变更\n", tabs, fieldName, fieldName)
			fmt.Fprintf(f, "%s\t\t}\n", tabs)
			fmt.Fprintf(f, "%s\t\tm.%s, changed = %s(nv), true\n", tabs, fieldName, fieldType.Elem().Name())
		}
		fmt.Fprintf(f, "%s\t\t}\n", tabs)
		fmt.Fprintf(f, "%s\t} else if nil != m.%s { // nv == nil 旧值 != nil\n", tabs, fieldName)
		fmt.Fprintf(f, "%s\t\tif record {\n", tabs)
		fmt.Fprintf(f, "%s\t\t\toldval[\"%s\"] = m.%s // 保存变更\n", tabs, fieldName, fieldName)
		fmt.Fprintf(f, "%s\t\t}\n", tabs)
		fmt.Fprintf(f, "%s\t\tm.%s, changed = nil, true\n", tabs, fieldName)
		fmt.Fprintf(f, "%s\t}\n", tabs)
		fmt.Fprintf(f, "%s}\n", tabs)
	} else {
		// float64 string 直接赋值给 m.x ,其余类型需要先转换成 float64
		fmt.Fprintf(f, "%s//[Value] Number,Bool,String; 由:genNormalField 函数生成\n", tabs)

		fmt.Fprintf(f, "%sif v, ok := n[\"%s\"]; ok {\n", tabs, jsonTag)
		fmt.Fprintf(f, "%s\tif nv, ok := v.(%s); ok && nv != m.%s {\n", tabs, fieldType.Name(), fieldName)
		fmt.Fprintf(f, "%s\t\tif record {\n", tabs)
		if fieldType.Name() == "float64" || fieldType.Name() == "string" || fieldType.Name() == "bool" {
			fmt.Fprintf(f, "%s\t\t\toldval[\"%s\"] = m.%s\n", tabs, fieldName, fieldName)
			fmt.Fprintf(f, "%s\t\t}\n", tabs)
			fmt.Fprintf(f, "%s\t\tm.%s, changed = nv, true\n", tabs, fieldName)
		} else {
			fmt.Fprintf(f, "%s\t\t\toldval[\"%s\"] = m.%s\n", tabs, fieldName, fieldName)
			fmt.Fprintf(f, "%s\t\t}\n", tabs)
			fmt.Fprintf(f, "%s\t\tm.%s, changed = %s(nv), true\n", tabs, fieldName, fieldType.Name())
		}
		fmt.Fprintf(f, "%s\t}\n", tabs)
		fmt.Fprintf(f, "%s}\n", tabs)
	}
}

/*
生成每一个字段的更新的代码.
name 结构体字段名称
tabs 前导缩进字符
*/
func generateUpdateField(f *os.File, tabs, jsoTag string, field reflect.StructField) {
	var (
		kind      = field.Type.Kind()
		isPointer = false
	)
	if kind == reflect.Ptr {
		isPointer = true
		kind = field.Type.Elem().Kind()
	}

	if kind == reflect.Slice {

	} else if kind == reflect.Array {
		fmt.Println(field.Name, "type:Array")
		// 生成的代码实现的功能。
		// 1. 比较长度，如果长度不一致，直接赋值。
		// 2. 如果长度一致，逐个元素比较，如果不一致，直接赋值。
		// 3. 如果长度一致，逐个元素比较，如果一致，不做任何操作。
		fmt.Fprintf(f, "%sif v, ok := n[\"%s\"]; ok {\n", tabs, jsoTag)
		fmt.Fprintf(f, "%s\tif len(m.%s) != len(v.([]interface{})) {\n", tabs, field.Name)
		fmt.Fprintf(f, "%s\t\tfor i, v := range v.([]interface{}) {\n", tabs)
		fmt.Fprintf(f, "%s\t\t\tm.%s[i] = v.(%s)\n", tabs, field.Name, field.Type.Elem().Name())
		fmt.Fprintf(f, "%s\t\t}\n", tabs)
		fmt.Fprintf(f, "%s\t} else {\n", tabs)
		fmt.Fprintf(f, "%s\t\tfor i, v := range v.([]interface{}) {\n", tabs)
		fmt.Fprintf(f, "%s\t\t\tif m.%s[i] != v.(%s) {\n", tabs, field.Name, field.Type.Elem().Name())
		fmt.Fprintf(f, "%s\t\t\t\tm.%s[i] = v.(%s)\n", tabs, field.Name, field.Type.Elem().Name())
		fmt.Fprintf(f, "%s\t\t\t}\n", tabs)
		fmt.Fprintf(f, "%s\t\t}\n", tabs)
		fmt.Fprintf(f, "%s\t}\n", tabs)
		fmt.Fprintf(f, "%s}\n", tabs)
	} else if kind == reflect.Struct {
		fmt.Fprintf(f, "%sif v, ok := n[\"%s\"]; ok {\n", tabs, jsoTag)
		if isPointer {
			fmt.Fprintf(f, "%s\tif v == nil {\n", tabs)
			fmt.Fprintf(f, "%s\t\tif m.%s != nil {\n", tabs, field.Name)
			fmt.Fprintf(f, "%s\t\t\tif record {\n", tabs)
			// 需要将原来的结构体转换为 map 然后交给 oldval 保存
			fmt.Fprintf(f, "%s\t\t\t\tvar o map[string]interface{}\n", tabs)
			fmt.Fprintf(f, "%s\t\t\t\tmapstructure.Decode(m.%s, &o)\n", tabs, field.Name)
			fmt.Fprintf(f, "%s\t\t\t\toldval[\"%s\"] = o\n", tabs, field.Name)
			fmt.Fprintf(f, "%s\t\t\t}\n", tabs)
			fmt.Fprintf(f, "%s\t\t\tm.%s = nil\n", tabs, field.Name)
			fmt.Fprintf(f, "%s\t\t\tchanged = true\n", tabs)
			fmt.Fprintf(f, "%s\t\t}\n", tabs)
			fmt.Fprintf(f, "%s\t} else {\n", tabs)
			fmt.Fprintf(f, "%s\t\tif m.%s == nil {\n", tabs, field.Name)
			fmt.Fprintf(f, "%s\t\t\tm.%s = &%s{}\n", tabs, field.Name, field.Type.Elem().Name())
			fmt.Fprintf(f, "%s\t\t}\n", tabs)
			fmt.Fprintf(f, "%s\t\tch, ov, _ := m.%s.update_(v.(map[string]interface{}), true, record)\n", tabs, field.Name)
			fmt.Fprintf(f, "%s\t\tif record && ch {\n", tabs)
			fmt.Fprintf(f, "%s\t\t\toldval[\"%s\"] = ov\n", tabs, field.Name)
			fmt.Fprintf(f, "%s\t\t}\n", tabs)
			fmt.Fprintf(f, "%s\t\tchanged = ch\n", tabs)
			fmt.Fprintf(f, "%s\t}\n", tabs)
			fmt.Fprintf(f, "%s}\n", tabs)
		} else {
			fmt.Fprintf(f, "%s\tif ch, ov, _ := m.%s.update_(v.(map[string]interface{}), create, record); ch {\n", tabs, field.Name)
			fmt.Fprintf(f, "%s\t\tif record {\n", tabs)
			fmt.Fprintf(f, "%s\t\t\toldval[\"%s\"] = ov\n", tabs, field.Name)
			fmt.Fprintf(f, "%s\t\t}\n", tabs)
			fmt.Fprintf(f, "%s\t\tchanged = true\n", tabs)
			fmt.Fprintf(f, "%s\t}\n", tabs)
			fmt.Fprintf(f, "%s}\n", tabs)
		}
	} else if kind == reflect.Map {

	} else if (kind >= reflect.Bool && kind <= reflect.Float64) || kind == reflect.String {
		genNormalField(f, tabs, field.Name, jsoTag, field.Type, isPointer)
	} else {
		fmt.Printf("unknown type: %s, %s", kind, field.Name)
	}
}

func tagContains(tag []string, search string) bool {
	for _, v := range tag {
		if v == search {
			return true
		}
	}
	return false
}

func generateUpdateFuncs(f *os.File, t reflect.Type) {
	fmt.Fprintf(f, "func (m *%s) update_(n map[string]interface{}, create,record bool) (bool, map[string]interface{}, error) {\n", t.Name())
	// 生成的代码实现的功能。
	fmt.Fprintf(f, "\tvar (\n")
	fmt.Fprintf(f, "\t\tchanged bool = false\n")
	fmt.Fprintf(f, "\t\toldval map[string]interface{} = nil\n")
	fmt.Fprintf(f, "\t)\n")
	fmt.Fprintf(f, "\tif record {\n")
	fmt.Fprintf(f, "\t\toldval = make(map[string]interface{})\n")
	fmt.Fprintf(f, "\t}\n")

	for i := 0; i < t.NumField(); i++ {
		var (
			field   = t.Field(i)
			jsoTag  = strings.Split(field.Tag.Get("json"), ",")
			attrTag = strings.Split(field.Tag.Get("attr"), ",")
		)
		if len(jsoTag) == 0 {
			continue
		}
		if tagContains(attrTag, "ro") {
			fmt.Fprintf(f, "\tif !create {\n")
			generateUpdateField(f, "\t\t", jsoTag[0], field)
			fmt.Fprintf(f, "\t}\n")
		} else {
			generateUpdateField(f, "\t", jsoTag[0], field)
		}
	}

	fmt.Fprintf(f, "\treturn changed, oldval, nil\n}\n\n")

	fmt.Fprintf(f, "func (m *%s) Update(n map[string]interface{}) (error) {\n", t.Name())
	fmt.Fprintf(f, "\t _, _, err := m.update_(n, false, false)\n\treturn err;\n}\n\n")
	fmt.Fprintf(f, "func (m *%s) UpdateWithCallback(n map[string]interface{}, onchange func(*%s, map[string]interface{})) (error) {\n", t.Name(), t.Name())
	fmt.Fprintf(f, "\tchanged, oldval, err := m.update_(n, false, true)\n")
	fmt.Fprintf(f, "\tif err != nil {\n")
	fmt.Fprintf(f, "\t\treturn err\n")
	fmt.Fprintf(f, "\t}\n")
	fmt.Fprintf(f, "\tif changed {\n")
	fmt.Fprintf(f, "\t\tonchange(m, oldval)\n")
	fmt.Fprintf(f, "\t}\n")
	fmt.Fprintf(f, "\treturn nil\n}\n\n")

}
